<?php

namespace Aoe\Emulator\Processor;

use Aoe\Database\Sprite;
use Aoe\Emulator\Ifc\Type;
use Aoe\Emulator\Pascal\PascalProvider;
use Aoe\Emulator\Schema\Element\Element;
use Aoe\Emulator\Schema\Element\Independent;
use Aoe\Emulator\Schema\Element\Relation;
use Aoe\Emulator\Schema\Model;
use Aoe\Util\Exception;
use PDO;
use Throwable;

/**
 * # 查询处理器
 *
 * @method int max($field = '*', $where = true, $model = null) 最大值
 * @method int min($field = '*', $where = true, $model = null) 最小值
 * @method int count($field = '*', $where = true, $model = null) 非空行数
 * @method int sum($field = '*', $where = true, $model = null) 和
 */
class Search extends Middle
{
    const string ERR_STATISTICS = '无效的统计词: [${name}]';
    const string ERR_ELEMENTS   = '没有查询域';
    
    const array stt_keys = ['count', 'sum', 'max', 'min', 'avg'];
    
    private array $wheres = [];
    
    private(set) ?Addition $addition = null {
        get {
            if (!$this->addition) {
                $this->addition = new Addition($this->model);
            }
            return $this->addition;
        }
    }

    private ?Condition $condition = null;
    
    /**
     * ## 添加查询条件
     *
     * @param array<scalar | Sprite | array{operator?: string, value?: mixed, name: string}> | int $terms
     *
     * @return static
     */
    public function orWhere(array|int $terms): static
    {
        if (!$this->condition) $this->condition = new Condition($this->model, $this->builder);
        
        $this->wheres[] = $this->condition->make($terms);
        return $this;
    }
    
    /**
     * ## 处理统计查询
     *
     * @param string $name 统计函数
     * @param        $arguments
     *
     * @return int
     * @throws Throwable
     */
    public function __call(string $name, $arguments): int
    {
        return $this->statistics($name);
    }
    
    /**
     * ## 统计查询
     *
     * @param string $name
     * @param string $element
     *
     * @return int
     * @throws Throwable
     */
    public function statistics(string $name, string $element = Type::ID): int
    {
        if (in_array($name, self::stt_keys)) {
            $r = $this->run("$name($element)", PDO::FETCH_NUM);
            return (int)($r[0][0]);
        }
        throw new Exception(self::ERR_STATISTICS, ['name' => $name]);
    }
    
    /**
     * ## 执行查询
     *
     * @param string|string[] $expression
     * @param ?int            $fetch_model
     *
     * @return array
     * @throws Throwable
     */
    public function run(array|string $expression, ?int $fetch_model = null): array
    {
        if ($this->id and empty($this->wheres)) $this->orWhere(['id' => $this->id]);
        
        return $this->builder->query(
            $this->table,
            $expression,
            $this->wheres,
            $this->addition->getOptions(),
            $fetch_model,
        );
    }

    /**
     * ## 查询单行
     *
     * @return array
     * @throws Throwable
     */
    public function row(): array
    {
        if (empty($this->id)) return [];
        $rs = $this->rows();
        
        if (empty($rs)) return [];
        
        return array_shift($rs);
    }
    
    /**
     * ## 实现了级联查询
     *
     * @return array
     * @throws Throwable
     */
    public function rows(): array
    {
        [$names, $columns] = $this->_map_element();
        
        $rs = empty($this->id) ?
            $this->builder->select($this->table, $columns, $this->wheres, $this->addition->getOptions())
            : $this->builder->selectById($this->table, $columns, $this->id);
        
        if (empty($rs)) return $rs;
        $this->id = array_unique(array_keys($rs));
        
        // 处理数据
        foreach ($names as $name) {
            $e = $this->model->getElement($name);
            
            try {
                if ($e->isRelation() && !$e->getRelateModel()->useJson()) {
                    $this->_belongs_value($e, $rs);
                    continue;
                }
            } catch (Throwable) {
            }
            
            $this->_decorate($e, $rs);
            
        }
        
        return $rs;
    }
    
    /**
     * @return array[]
     * @throws Throwable
     */
    private function _map_element(): array
    {
        $names      = [];
        $columns = [];
        /** @var Element $element */
        foreach ($this->model->getElementIterator($this->elements) as $element) {
            $names[]      = $element->name;
            $columns[] = $element->getColumn();
        }
        
        //没有查询域
        if (empty($names) or $names === [Type::ID]) throw new Exception(self::ERR_ELEMENTS);
        return [$names, $columns];
    }
    
    /**
     * ## 简单的直接查询
     *
     * @param ?int $fetch_model
     *
     * @return array
     * @throws Throwable
     *
     * @noinspection PhpUnused
     */
    public function select(?int $fetch_model = null): array
    {
        $columns = [];
        /** @var Element $electron */
        foreach ($this->model->getElementIterator($this->elements) as $electron) {
            $columns[] = $electron->getColumn();
        }
        return $this->run($columns, $fetch_model);
    }
    
    private function _belongs_value(Relation $relation, array &$rs): void
    {
        $column = $relation->getColumn();
        $ids       = array_column_unique($rs, $column);
        try {
            $c      = $relation->getRelateModel();
            $key    = $c->getKey();
            $values = $this->_get_relation_values($c, $ids);
        } catch (Throwable) {
            return;
        }
        $name = $relation->name;
        foreach ($rs as &$record) {
            $id = (int)$record[$column];
            
            $record[$name]    = $id;
            $record["#$name"] = $values[$id][$key] ?? $id;
        }
    }
    
    /**
     * @param Model $m
     * @param array $ids
     *
     * @return array
     * @throws Throwable
     */
    private function _get_relation_values(Model $m, array $ids): array
    {
        $key = $m->getKey();
        return $m->useJson() ?
            Texture::get($m)->getRowsByIds($ids, $key) :
            $this->_fork($m)->orWhere([Type::ID => $ids])->setElements([$key])->rows();
    }
    
    /**
     * @param string[] $elements
     *
     * @return static
     */
    public function setElements(array $elements): static
    {
        $this->elements = $elements;
        return $this;
    }
    
    private function _fork(?Model $collection = null): static
    {
        if ($collection === null) $collection = $this->model;
        return new static($collection, $this->builder);
    }
    
    /**
     * 处理数据库读取到的数据
     *
     * @param Independent $independent
     * @param array       $rs
     */
    private function _decorate(Independent $independent, array &$rs): void
    {
        $column = $independent->getColumn();
        $pascal    = PascalProvider::get($independent);
        $name      = $independent->name;
        
        foreach ($rs as &$record) {
            $raw     = $record[$column];
            $value   = $pascal->afterLoad($raw);
            $display = $pascal->display($value);
            
            $record[$name] = $value;
            // #name  = 展示值
            if ($display !== $value) $record["#$name"] = $display;
        }
    }
    
    /**
     * @throws Throwable
     * @noinspection PhpUnused
     */
    public function getIds(): array
    {
        $r = array_values($this->run([Type::ID]));
        return array_column_unique($r, Type::ID);
    }
}